﻿using MiniFox.Caching;
using MiniFox.Net;
using MiniFox.Utility;
using MiniFox.Web;
using System.Collections.Specialized;
using System.Net.Mime;
using System.Text;
using Wechat.Core.Models;

namespace Wechat.Core
{
    /// <summary>
    /// 
    /// </summary>
    public abstract class WechatServiceBase : IWechatService
    {
        const string ACCESS_TOKEN_KEY = "accesstoken";
        static readonly object  _accessTokenLocker;
        static WechatServiceBase()
        {
            _accessTokenLocker = new object();
        }
        /// <summary>
        /// 
        /// </summary>
        public abstract string Name
        {
            get;
        }
        /// <summary>
        /// 
        /// </summary>
        protected string AccessTokenCacheKey
        {
            get
            {
                var key = $"{ApiGlobalKey.AREA_NAME}#{this.Name}#{ACCESS_TOKEN_KEY}";
                return key;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        public abstract Uri AccessTokenUri
        {
            get;
        }

        CacheProvider _cacheProvider;
        /// <summary>
        /// 
        /// </summary>
        public CacheProvider CacheProvider
        {
            get
            {
                if (_cacheProvider == null)
                {
                    _cacheProvider = CacheService.GetCacheProvider("Wechat");
                }
                return _cacheProvider;
            }
        }

        #region access token
        /// <summary>
        /// 
        /// </summary>
        public string AccessToken
        {
            get
            {
                var accessToken = this.CacheProvider.Get<AccessTokenResponse>(this.AccessTokenCacheKey);
                lock (_accessTokenLocker)
                {
                    if (accessToken == null)
                    {
                        accessToken = this.GetAccessTokenAsync().Result;
                        this.CacheProvider.Add(this.AccessTokenCacheKey, accessToken, accessToken.ExpiresIn);
                    }
                }
                return accessToken.Token;
            }
        }

        /// <summary>
        /// open api 调用时使用
        /// </summary>
        /// <returns></returns>
        protected abstract Task<AccessTokenResponse> GetAccessTokenAsync();
        /// <summary>
        /// 
        /// </summary>
        public virtual void ResetAccessToken()
        {
            this.CacheProvider.Remove(this.AccessTokenCacheKey);
        }

        #endregion
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="requestData"></param>
        /// <param name="adapterName"></param>
        /// <param name="serviceName"></param>
        /// <param name="method"></param>
        /// <param name="queryString"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public async Task<string> InvokeAPIAsync(string adapterName, string serviceName, string method, NameValueCollection queryString, NameValueCollection headers, string requestData = null)
        {
            var httpAdapter = WebAdapterFactory.Container.GetHttpAdapterByName(adapterName);
            var httpService = httpAdapter.GetService(serviceName);
            var accessToken = this.AccessToken;
            queryString.Remove(ApiGlobalKey.ACCESS_TOKEN);
            queryString.Add(ApiGlobalKey.ACCESS_TOKEN, accessToken);
            DateTime startTime = DateTime.Now;

            StringContent content = string.IsNullOrEmpty(requestData) ? null : new StringContent(requestData);
            var response = await httpService.SendAsync(new HttpMethod(method), queryString, headers, content);
            string responseData = await response.Content.ReadAsStringAsync();
            DateTime endTime = DateTime.Now;
            int elapsed = (int)(endTime - startTime).TotalMilliseconds;
            string url = httpService.FullURI.ToString();

            this.WriteLog(url, method, requestData, responseData, elapsed);


            return responseData;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="uri"></param>
        /// <param name="mode"></param>
        /// <returns></returns>
        public async virtual Task<T> HttpGetAsync<T>(Uri uri, SerializeMode mode = SerializeMode.Json) where T : StandardResponse
        {
            DateTime startTime = DateTime.Now;
            var response = await uri.HttpGetAsync(uri);
            string data = await response.ReadAsStringAsync();
            DateTime endTime = DateTime.Now;

            int elapsed = (int)(endTime - startTime).TotalMilliseconds;
            this.WriteLog(uri.ToString(), "get", string.Empty, data, elapsed);

            T responseObj = mode == SerializeMode.Xml ? data.ToXMLObject<T>() : data.ToJsonObject<T>();
            if (responseObj.ErrorCode != 0)
            {
                if (responseObj.ErrorCode == 42001)
                {
                    ResetAccessToken();
                    var accessToken = this.AccessToken;
                    uri = uri.RemoveQueryString(ApiGlobalKey.ACCESS_TOKEN);
                    uri = uri.AddQueryString(ApiGlobalKey.ACCESS_TOKEN, accessToken);

                    return await HttpGetAsync<T>(uri, mode);
                }
                else
                {
                    var e = new WechatException(responseObj.ErrorCode, responseObj.ErrorMessage)
                    {
                        RequestUri = uri.ToString()
                    };
                    throw e;
                }
            }
            return responseObj;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TR"></typeparam>
        /// <typeparam name="TS"></typeparam>
        /// <param name="uri"></param>
        /// <param name="requestObj"></param>
        /// <param name="mode"></param>
        /// <returns></returns>
        public async virtual Task<TS> HttpPostAsync<TR, TS>(Uri uri, TR requestObj, SerializeMode mode = SerializeMode.Json) where TR : class where TS : StandardResponse
        {
            string requestData = mode == SerializeMode.Xml ? requestObj.ToXMLString() : requestObj.ToJsonString();
            DateTime startTime = DateTime.Now;
            var content = new StringContent(requestData, Encoding.UTF8, mode == SerializeMode.Xml ? MediaTypeNames.Application.Xml : MediaTypeNames.Application.Json);
            var response = await uri.HttpPostAsync(content);
            string responseData = await response.ReadAsStringAsync();
            DateTime endTime = DateTime.Now;

            int elapsed = (int)(endTime - startTime).TotalMilliseconds;
            this.WriteLog(uri.ToString(), "post", requestData, responseData, elapsed);

            TS responseObj = mode == SerializeMode.Xml ? responseData.ToXMLObject<TS>() : responseData.ToJsonObject<TS>();
            if (responseObj.ErrorCode != 0)
            {
                if (responseObj.ErrorCode == 42001)
                {
                    ResetAccessToken();
                    var accessToken = this.AccessToken;
                    uri = uri.RemoveQueryString(ApiGlobalKey.ACCESS_TOKEN);
                    uri = uri.AddQueryString(ApiGlobalKey.ACCESS_TOKEN, accessToken);

                    return await HttpPostAsync<TR, TS>(uri, requestObj, mode);
                }
                else
                {
                    var e = new WechatException(responseObj.ErrorCode, responseObj.ErrorMessage)
                    {
                        RequestUri = uri.ToString(),
                        RequestData = requestObj
                    };
                    throw e;
                }
            }
            return responseObj;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="url"></param>
        /// <param name="method"></param>
        /// <param name="requestData"></param>
        /// <param name="responseData"></param>
        /// <param name="elapsed"></param>
        protected abstract void WriteLog(string url, string method, string requestData, string responseData, int elapsed);

        /// <summary>
        /// 
        /// </summary>
        public abstract string Token
        {
            get;
        }
    }
}
